#include "serialport_mgr.h"

#include <QDebug>
#include <QMutex>
#include <QDataStream>
#include <QCoreApplication>
#include <QTime>
#include "def_serialport.h"
#include "share_function.h"

static SerialportMgr *g_pSerialportMgr= nullptr;
static QMutex g_mtxLockSend;
static QMutex g_mtxLockRecv;
static QList<QByteArray> g_lstSndBuf;
static QList<QByteArray> g_lstFetchBuf;

/*
 * Function:    SerialportMgr
 * Description: The default constructor
 * Paramer:     void
 * Return:
 */
SerialportMgr::SerialportMgr(QObject *parent) : QThread(parent)
{
    qDebug() << Q_FUNC_INFO;
}

/*
 * Function:    ~SerialportMgr
 * Description: The default destructor
 * Paramer:     void
 * Return:
 */
SerialportMgr::~SerialportMgr()
{
    qDebug() << Q_FUNC_INFO;

    m_bRunning = false;
    QThread::quit();
    QThread::wait();

    if(m_pWriteDataTimer) {
        if(m_pWriteDataTimer->isActive()) {
            m_pWriteDataTimer->stop();
            m_pWriteDataTimer = nullptr;
        }
    }

    // close COM
    if(m_pSerialPort) {
        if(m_pSerialPort->isOpen()) {
            m_pSerialPort->clear();
            m_pSerialPort->close();
            m_pSerialPort->deleteLater();
            m_pSerialPort = nullptr;
        }
    }

    // destroy the object
    if(g_pSerialportMgr) {
        g_pSerialportMgr->deleteLater();
        g_pSerialportMgr = nullptr;
    }
}

/*
 * Function:    getInstance
 * Description: Get an instance of "SerialportMgr" class, if exist, return it
 * Paramer:     QObject *pParent - the pointer of QObject class
 * Return:      the pointer of "SerialportMgr" class
 */
SerialportMgr* SerialportMgr::getInstance(QObject *pParent)
{
    qDebug() << Q_FUNC_INFO;

    if(!g_pSerialportMgr) {
        // Create a new object if it doesn't exist
        g_pSerialportMgr = new SerialportMgr(pParent);
        if(!g_pSerialportMgr) return nullptr;

        // After constructing object, initialize the object
        g_pSerialportMgr->_initialze();
    }

    // Set the parent object
//    g_pSerialportMgr->setParent((QThread*)pParent);

    return g_pSerialportMgr;
}

/*
 * Function:    _initialze
 * Description: After constructing object, initialize the object
 * Paramer:     void
 * Return:      void
 */
void SerialportMgr::_initialze()
{
    qDebug() << Q_FUNC_INFO;

    if(false) {
        QByteArray baOutput;
        QString sBak = "40 4d 44 78 15 00 03 00 00 00 03 00 00 00 42 00 7b 22 69 64 22 3a 32 35 34 2c 22 6d 65 74 68 6f 64 22 3a 22 68 65 61 72 74 42 65 61 74 22 2c 22 72 65 73 75 6c 74 22 3a 30 2c 22 70 61 72 61 6d 73 22 3a 5b 32 30 30 30 30 2c 20 35 2c 20 30 5d 7d 00 41 c0";
        QString sInput2 = "40 4d 44 78 15 00 03 00 00 00 03 00 00 00 42 00 7b 22 69 64 22 3a 32 35 34 2c 22 6d 65 74 68 6f 64 22 3a 22 68 65 61 72 74 42 65 61 74 22 2c 22 72 65 73 75 6c 74 22 3a 30 2c 22 70 61 72 61 6d 73 22 3a 5b 32 30 30 30 30 2c 20 35 2c 20 30 5d 7d 00";
        QString sInput = "40 4d 44 78 15 00 02 00 00 00 10 00 00 00 00 00 ";
        QStringList lstHex = sInput.trimmed().split(" ");
        for(const auto & sItem : lstHex) {
            if(!sItem.isEmpty()) {
                baOutput.append((char)sItem.toInt(0, 16) & 0xff);
            }
        }

        unsigned char *pData = (unsigned char *)baOutput.data();
        quint16 unCRC = GetCRC16(pData, baOutput.size(), 0);

        QByteArray baHex;
        unsigned char ucHigh = (unCRC >> 8) & 0xff;
        unsigned char ucLow = unCRC & 0xff;
        baHex.push_back(ucHigh);
        baHex.push_back(ucLow);
        qDebug() << __FUNCTION__ << baHex.toHex(' ');
    }
}

/*
 * Function:    openCOM
 * Description: open a serial port
 * Paramer:     void
 * Return:      int - the error code
 */
int SerialportMgr::openCOM()
{
    qDebug() << Q_FUNC_INFO;

    int nRetVal = -1;

    // create an timer
    if(!m_pWriteDataTimer) m_pWriteDataTimer = new QTimer();
    Q_ASSERT(m_pWriteDataTimer);

    // instantiate a serialport object
    if(!m_pSerialPort) m_pSerialPort = new QSerialPort();
    Q_ASSERT(m_pSerialPort);

    // Configure COM parameters
    m_pSerialPort->setPortName(DEF_COM_MACHINE);
    m_pSerialPort->setBaudRate(QSerialPort::Baud115200);
    m_pSerialPort->setDataBits(QSerialPort::Data8);
    m_pSerialPort->setParity(QSerialPort::NoParity);
    m_pSerialPort->setStopBits(QSerialPort::OneStop);
    m_pSerialPort->setFlowControl(QSerialPort::NoFlowControl);

    // Signal-slot initialization
    std::vector<bool> vecConnect;
    vecConnect.push_back(QObject::connect(m_pSerialPort, SIGNAL(readyRead()), this, SLOT(slotDataReceived()), Qt::QueuedConnection));
    vecConnect.push_back(QObject::connect(m_pWriteDataTimer, SIGNAL(timeout()), this, SLOT(slotSendDataTimer())));
    for(int i=0; i<(int)vecConnect.size(); i++) {
      Q_ASSERT(vecConnect.at(i));
    }

    // close COM firstly
    if(m_pSerialPort->isOpen()) {
        m_pSerialPort->clearError();
        m_pSerialPort->clear();
        m_pSerialPort->close();
    }

    // Open COM
    if(m_pSerialPort->open(QIODevice::ReadWrite)) {
        m_pSerialPort->clearError();
        m_pSerialPort->clear();

        nRetVal = 0;
    }
    else {
        qDebug() << Q_FUNC_INFO << m_pSerialPort->errorString();
    }

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
 * Function:    closeCOM
 * Description: close a serial port
 * Paramer:     void
 * Return:      void
 */
void SerialportMgr::closeCOM()
{
    qDebug() << Q_FUNC_INFO;

    if(m_pSerialPort) QObject::disconnect(m_pSerialPort, SIGNAL(readyRead()), this, SLOT(slotDataReceived()));
    if(m_pWriteDataTimer) QObject::disconnect(m_pWriteDataTimer, SIGNAL(timeout()), this, SLOT(slotSendDataTimer()));

    // stop timer
    if(m_pWriteDataTimer) {
        if(m_pWriteDataTimer->isActive()) {
            m_pWriteDataTimer->stop();
            m_pWriteDataTimer = nullptr;
        }
    }

    // close COM
    if(m_pSerialPort) {
        if(m_pSerialPort->isOpen()) {
            m_pSerialPort->clear();
            m_pSerialPort->close();
            m_pSerialPort->deleteLater();
            m_pSerialPort = nullptr;
        }
    }

    // destroy the object
    if(g_pSerialportMgr) {
        g_pSerialportMgr->deleteLater();
        g_pSerialportMgr = nullptr;
    }
}

/*
 * Function:    writeData
 * Description: write data into COM
 * Paramer:     const QByteArray &baData - the data buffer
 *              bool bQueued - is data queued?
 * Return:      void
 */
int SerialportMgr::writeData(const QByteArray &baInput, const bool bQueued)
{
    qDebug() << Q_FUNC_INFO << bQueued << baInput.size();

    if(!bQueued) {
        if(0 < baInput.size()) {
            if(m_pSerialPort->isWritable()) {
                g_mtxLockSend.lock();
                qDebug() << Q_FUNC_INFO << baInput.size() << baInput.toHex(' ');

                int nWriteData = m_pSerialPort->write(baInput.data(), baInput.size());
                bool bFLush = m_pSerialPort->flush();
                g_mtxLockSend.unlock();

                qDebug() << Q_FUNC_INFO << nWriteData << bFLush;
                return nWriteData;
            }
        }
    }
    else {
        if(!baInput.isEmpty()) {
            g_lstSndBuf << baInput;
            m_pWriteDataTimer->start(10);
        }
    }

    qDebug() << Q_FUNC_INFO << baInput.size() << baInput.toHex(' ');
    return 0;
}

/*
 * Function:    fetchData
 * Description: send data & receive data
 * Paramer:     QByteArray &baOutput - the output buffer
 *              const QByteArray &baInput - the input buffer
 *              const int nSleep - Wait time before reading data
 * Return:      int - the size of receive buffer
 */
int SerialportMgr::fetchData(QByteArray &baOutput, const QByteArray &baInput, const int nSleep)
{
    qDebug() << Q_FUNC_INFO << baInput.size() << baInput.toHex(' ') << baInput << nSleep;
    baOutput.clear();

    int nRetVal = 0;

    g_lstFetchBuf.clear();

    if(0 < baInput.size()) {
        if(m_pSerialPort->isWritable()) {
            g_mtxLockSend.lock();
            int nWriteData = m_pSerialPort->write(baInput.data(), baInput.size());
            bool bFLush = m_pSerialPort->flush();
            g_mtxLockSend.unlock();

            qDebug() << Q_FUNC_INFO << nWriteData << bFLush;

            // Thread sleeps for several milliseconds
            QTime tmrSleep = QTime::currentTime().addMSecs(nSleep);
            while(QTime::currentTime() < tmrSleep) {
                // Force to enter the event loop of the current thread to avoid program stagnation
                QCoreApplication::processEvents(QEventLoop::AllEvents, 100);

                if(0 < g_lstFetchBuf.size()) {
                    baOutput = g_lstFetchBuf.takeFirst();

                    nRetVal = baOutput.size();

                    qDebug() << Q_FUNC_INFO << baOutput.size() << baOutput.toHex(' ') << baOutput;
                    break;
                }
            }
        }
    }

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
 * Function:    slotDataReceived
 * Description: Received data from COM
 * Paramer:     QByteArray baData - the data buffered
 * Return:      void
 */
void SerialportMgr::slotDataReceived()
{
    qDebug() << Q_FUNC_INFO << m_pSerialPort;

    if(m_pSerialPort->isReadable()) {
        g_mtxLockRecv.lock();

        m_pSerialPort->waitForReadyRead(100);

        QByteArray baRecv = m_pSerialPort->readAll();
        qDebug() << Q_FUNC_INFO << baRecv.size() << baRecv.toHex(' ');

        if(0 < baRecv.size()) {
            // Concatenated data packet from COM
            this->_concatenatedData(baRecv);
        }

        g_mtxLockRecv.unlock();
    }
}

/*
 * Function:    slotPayloadFetched
 * Description: Fetch data from COM
 * Paramer:     const QByteArray &baData - the data package
 * Return:      void
 */
void SerialportMgr::slotPayloadFetched(const QByteArray &baData)
{
    qDebug() << Q_FUNC_INFO << baData.size() << baData.toHex(' ') << baData;

    if(0 < baData.size()) {
        g_lstFetchBuf << baData;
    }
}

/*
 * Function:    slotReplyACK
 * Description: reply an ACK command to COM
 * Paramer:     QByteArray baPacket - the data package
 *              bool bQueued - is process later
 * Return:      int - the length of data size
 */
int SerialportMgr::slotReplyACK(QByteArray baPacket, bool bQueued)
{
    qDebug() << Q_FUNC_INFO << baPacket.size() << bQueued;
    int nRetVal = 0;

    if(DEF_PKG_FIRST_PART < baPacket.size()) {
        QByteArray baACK;
        for(int i=0; i<DEF_PKG_FIRST_PART; i++) {
            baACK.push_back(baPacket.at(i));
        }

        // set the CMD
        baACK[6] = E_PKG_CT_ACK;

        // set the data length
        baACK[DEF_PKG_FIRST_PART-2] = 0x0;
        baACK[DEF_PKG_FIRST_PART-1] = 0x0;

        unsigned char *pData = (unsigned char *)baACK.data();
        quint16 unCRC = GetCRC16(pData, baACK.size(), 0);

        QByteArray baHex;
        unsigned char ucHigh = (unCRC >> 8) & 0xff;
        unsigned char ucLow = unCRC & 0xff;
        baACK.push_back(ucHigh);
        baACK.push_back(ucLow);

        this->writeData(baACK, bQueued);

        nRetVal = baACK.size();
    }

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
 * Function:    slotSendDataTimer
 * Description: write data to COM
 * Paramer:     void
 * Return:      void
 */
void SerialportMgr::slotSendDataTimer()
{
    qDebug() << Q_FUNC_INFO;

    // stop the timer firstly
    m_pWriteDataTimer->stop();

    // Send the command handler
    this->_commandHandler();
}

/*
 * Function:    run
 * Description: default thread function
 * Paramer:     void
 * Return:      void
 */
void SerialportMgr::run()
{
    while(m_bRunning) {
        // Send the command handler
        this->_commandHandler();

        qApp->processEvents();
        QThread::msleep(10);
    }
}

/*
 * Function:    _commandHandler
 * Description: the command handler
 * Paramer:     void
 * Return:      void
 */
void SerialportMgr::_commandHandler()
{
    qDebug() << Q_FUNC_INFO << g_lstSndBuf.size();
    QByteArray baData = g_lstSndBuf.takeFirst();

    if(0 < baData.size()) {
        if(m_pSerialPort->isWritable()) {
            g_mtxLockSend.lock();
            int nWriteData = m_pSerialPort->write(baData.data(), baData.size());
            bool bFLush = m_pSerialPort->flush();
            g_mtxLockSend.unlock();

            qDebug() << Q_FUNC_INFO << nWriteData << bFLush << baData.size() << baData.toHex(' ');
        }
    }
}

/*
 * Function:    _concatenatedData
 * Description: Concatenated data packet from COM
 * Paramer:     QByteArray & baOutput - the data buffer for packet
 *              QByteArray baInput - the data buffer for current data
 * Return:      int - the length of readed
 */
int SerialportMgr::_concatenatedData(QByteArray baRecv)
{
    qDebug() << Q_FUNC_INFO << baRecv.size() << m_bufRecv.size() << baRecv;

#ifndef QT_PROJECT_T1
    emit signalDataReceived(baRecv);
    return baRecv.size();
#endif

    m_bufRecv.write(baRecv.data(), baRecv.length());

do_proc:
    while(0 < m_bufRecv.size()) {
        // If the value (DEF_MIN_PKG_SIZE) is too large, the same serial port packet will be parsed many times. Please be careful
        if(DEF_MIN_PKG_SIZE > m_bufRecv.size()) break;

        // Delete excess data before the command header (0x40 0x4d 0x44 0x78)
        while (4 < m_bufRecv.size()) {
            if((DEF_PKG_HEADER1 == (unsigned char)m_bufRecv[0])
                    && (DEF_PKG_HEADER2 == (unsigned char)m_bufRecv[1])
                    && (DEF_PKG_HEADER3 == (unsigned char)m_bufRecv[2])
                    && (DEF_PKG_HEADER4 == (unsigned char)m_bufRecv[3])) {
                break;
            }

            m_bufRecv.pop_front();
        }

        if(DEF_MIN_PKG_SIZE <= m_bufRecv.size()) {
            // for example: 40 4d 44 78 15 00 02 01 00 00 00 00 00 00 00 00 04 3e
            if((DEF_PKG_HEADER1 == (unsigned char)m_bufRecv[0])
                    && (DEF_PKG_HEADER2 == (unsigned char)m_bufRecv[1])
                    && (DEF_PKG_HEADER3 == (unsigned char)m_bufRecv[2])
                    && (DEF_PKG_HEADER4 == (unsigned char)m_bufRecv[3])) {
                unsigned short usRecvDataLen = ((unsigned char)m_bufRecv[15] << 8) | (unsigned char)m_bufRecv[14];
                unsigned int unPkgLen = DEF_PKG_MIN_SIZE + usRecvDataLen;

                qDebug() << Q_FUNC_INFO << m_bufRecv.size() << unPkgLen << usRecvDataLen;

                if(unPkgLen <= m_bufRecv.size())  {
                    qDebug() << Q_FUNC_INFO << m_bufRecv.size();

                    int nOffsetCRC = DEF_PKG_FIRST_PART + usRecvDataLen;
                    unsigned short usOldCRC = ((unsigned char)m_bufRecv[nOffsetCRC + 0]) << 8 | (unsigned char)(m_bufRecv[nOffsetCRC + 1]);
                    unsigned short usNewCRC = GetCRC16(((const unsigned char *)(&m_bufRecv[0])), nOffsetCRC, 0);
                    qDebug() << Q_FUNC_INFO << unPkgLen << nOffsetCRC << usRecvDataLen << usOldCRC << usNewCRC;

                    if(usOldCRC != usNewCRC) {

                        // Delete the header & process data again
                        for(int j=0; j<DEF_PKG_SYNC_SIZE; j++) {
                            m_bufRecv.pop_front();
                        }

                        goto do_proc;
                    }

                    QByteArray baPacket;
                    baPacket.resize(unPkgLen);
                    m_bufRecv.read(baPacket.data(), unPkgLen);

                    qDebug() << Q_FUNC_INFO << baPacket.size() << baPacket.toHex(' ');

                    if(0 < baPacket.size()) {
                        // send a signal tell "SerialportPlugin" class
                        emit signalDataReceived(baPacket);
                    }
                }
                else {
                    goto do_exit;
                }
            }
        }
    }

do_exit:
    qDebug() << Q_FUNC_INFO << m_bufRecv.size();
    return m_bufRecv.size();
}
